package com.wimcorp.magic.phase;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.wimcorp.magic.abilities.Ability;
import com.wimcorp.magic.board.CardType;
import com.wimcorp.magic.cards.Creature;
import com.wimcorp.magic.cards.PlayingCard;
import com.wimcorp.magic.exception.BlockerException;
import com.wimcorp.magic.players.Player;
import com.wimcorp.magic.spell.Castable;

public class CombatPhase extends Phase {

	private Set<Ability> combatObservers = new HashSet<Ability>();
	private Set<Ability> attackDeclareObservers = new HashSet<Ability>();
	private Set<Ability> blockerDeclareObservers = new HashSet<Ability>();
	private Set<Ability> damageDealtObservers = new HashSet<Ability>();

	public CombatPhase(Player activePlayer) {
		super(activePlayer);
	}

	public void execute() {
		begin();
		if (getStack().size() != 0) {
			activateAbilities();
		}
		declareAttackers();
		// declareTargets();
		// payAttackCost();
		// activateAbilities();
		declareBlockers();
		// payBlockCost();
		// activateAbilities();
		// assignAttackDamage();
		// assignDefenseDamage();
		resolveAttack();
		activateAbilities();
	}

	// beginning of combat
	public void begin() {
		// trigger abilities at beginning of combat phase
		for (Ability combatObserver : combatObservers) {
			getActivePlayer().cast(combatObserver);
		}
		if (getStack().size() != 0) {
			// resolve stack
			getStack().resolve();
		}
	}

	// declare attackers
	public void declareAttackers() {
		// trigger abilities on attackers being declared
		Player player = getActivePlayer();
		for (Ability attackDeclareObserver : attackDeclareObservers) {
			player.cast(attackDeclareObserver);
		}
		if (getStack().size() != 0) {
			// resolve stack
			getStack().resolve();
		}
		if (player.getSummoningPlaines().getAttackeableCreatureCountOnBattlefield() > 0) {
			try {
				Long[] attackers = getActivePlayerUserInterface().declareAttackers();
				ArrayList<Creature> creatures = new ArrayList<>();
				for (long attacker : attackers) {
					Creature creature = (Creature) player.getSummoningPlaines().getCard(CardType.ALL, attacker);
					if (creature != null) {
						creatures.add(creature);
					} 
				}
				player.declareAttackers(player.getOpponent(), creatures);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		} else {
			messagePlayer("Skipping declare attackers phase - You have no creatures that can attack. ");
		}
	}

	// declare blockers
	public void declareBlockers() {
		Player attackingPlayer = getActivePlayer();
		Player defendingPlayer = attackingPlayer.getOpponent();
		Set<Creature> attackers = attackingPlayer.getSummoningPlaines().getAttackersOnPlayer(defendingPlayer);
		if (attackers != null && attackers.size() > 0) {
			if (defendingPlayer.getSummoningPlaines().getBlockableCreatureCountOnBattlefield() > 0) {
				try {
					Set<Long> attackerIds = new HashSet<>();
					for (Creature creature : attackers) {
						attackerIds.add(creature.getIdentifier());
					}
					boolean error = false;
					do {
						error = false;
						Map<Long, Long[]> result = defendingPlayer.getUserInterface().declareBlockers(attackerIds);
						for (Long attacker : result.keySet()) {
							PlayingCard attackingCreature = attackingPlayer.getSummoningPlaines().getCard(
									CardType.ATTACKER, attacker);
							List<Creature> defendingCreatures = new ArrayList<>();
							for (long blockerId : result.get(attacker)) {
								defendingCreatures.add((Creature) defendingPlayer.getSummoningPlaines().getCard(
										CardType.ALL, blockerId));
							}
							try {
								defendingPlayer.declareBlocker((Creature) attackingCreature, defendingCreatures);
							} catch (BlockerException e) {
								error = true;
								defendingPlayer.getSummoningPlaines().resetBlockers();
								messageOpponent(e.getMessage());
							}
						}
					} while (error);
				} catch (RemoteException e) {
					e.printStackTrace();
				}
				// trigger abilities on defenders being declared
				for (Ability blockerDeclareObserver : blockerDeclareObservers) {
					attackingPlayer.cast(blockerDeclareObserver);
				}
				if (getStack().size() != 0) {
					// resolve stack
					getStack().resolve();
				}
			} else {
				messageOpponent("Skipping declare blocksers phase - You have no creatures that can block. ");

			}
		} else {
			messageOpponent("Your opponent " + getActivePlayer().getName() + " chooses not to attack this turn. ");
		}

	}

	public void resolveAttack() {
		// trigger abilities on damage being assigned or dealt
		for (Ability damageDealtObserver : damageDealtObservers) {
			getActivePlayer().cast(damageDealtObserver);
		}
		if (getStack().size() != 0) {
			// resolve stack
			getStack().resolve();
		}
		getActivePlayer().resolveAttack(getActivePlayer().getOpponent());
	}

	public void registerCombatObserver(Ability ability) {
		combatObservers.add(ability);
	}

	public void unRegisterCombatObserver(Ability ability) {
		combatObservers.remove(ability);
	}

	public void registerAttackDeclareObserver(Ability ability) {
		attackDeclareObservers.add(ability);
	}

	public void unRegisterAttackDeclareObserver(Ability ability) {
		attackDeclareObservers.remove(ability);
	}

	public void registeBlockerDeclareObserver(Ability ability) {
		blockerDeclareObservers.add(ability);
	}

	public void unRegisterBlockerDeclareObserver(Ability ability) {
		blockerDeclareObservers.remove(ability);
	}

	public void registeDamageDealtObserver(Ability ability) {
		damageDealtObservers.add(ability);
	}

	public void unRegisterDamageDealtObserver(Ability ability) {
		damageDealtObservers.remove(ability);
	}

}
